using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

namespace WorkFlow.Tools.UI
{
    [AddComponentMenu("Layout/Custom Content Size Fitter", 140)]
    [ExecuteAlways]
    [RequireComponent(typeof(RectTransform))]
    public class CustomContentSizeFitter : UIBehaviour, ILayoutSelfController
    {
        /// <summary>
        /// The size fit modes avaliable to use.
        /// </summary>
        public enum FitMode
        {
            /// <summary>
            /// Don't perform any resizing.
            /// </summary>
            Unconstrained,

            /// <summary>
            /// Resize to the minimum size of the content.
            /// </summary>
            MinSize,

            /// <summary>
            /// Resize to the preferred size of the content.
            /// </summary>
            PreferredSize
        }

        [SerializeField] private bool controlSelf = true;
        [SerializeField] protected FitMode m_HorizontalFit = FitMode.Unconstrained;
        [SerializeField] protected Vector2 widthRange;
        
        public Vector2 WidthRange
        {
            get => widthRange;
            set => widthRange = value;
        }

        /// <summary>
        /// The fit mode to use to determine the width.
        /// </summary>
        public FitMode horizontalFit
        {
            get { return m_HorizontalFit; }
            set
            {
                if (SetStruct(ref m_HorizontalFit, value))
                    SetDirty();
            }
        }

        [SerializeField] protected FitMode m_VerticalFit = FitMode.Unconstrained;
        [SerializeField] protected Vector2 heightRange;

        public Vector2 HeightRange
        {
            get => heightRange;
            set => heightRange = value;
        }

        /// <summary>
        /// The fit mode to use to determine the height.
        /// </summary>
        public FitMode verticalFit
        {
            get { return m_VerticalFit; }
            set
            {
                if (SetStruct(ref m_VerticalFit, value)) SetDirty();
            }
        }

        [System.NonSerialized] private RectTransform m_Rect;

        private RectTransform rectTransform
        {
            get
            {
                if (m_Rect == null)
                    m_Rect = GetComponent<RectTransform>();
                return m_Rect;
            }
        }

        // field is never assigned warning
#pragma warning disable 649
        private DrivenRectTransformTracker m_Tracker;
#pragma warning restore 649

        protected CustomContentSizeFitter()
        {
        }

        protected override void OnEnable()
        {
            base.OnEnable();
            SetDirty();
        }

        protected override void OnDisable()
        {
            m_Tracker.Clear();
            LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
            base.OnDisable();
        }

        public void ForceRefresh()
        {
            HandleSelfFittingAlongAxis(0);
            HandleSelfFittingAlongAxis(1);
            SetDirty();
        }
        protected override void OnRectTransformDimensionsChange()
        {
            SetDirty();
        }

        private void HandleSelfFittingAlongAxis(int axis)
        {
            FitMode fitting = (axis == 0 ? horizontalFit : verticalFit);
            if (fitting == FitMode.Unconstrained)
            {
                // Keep a reference to the tracked transform, but don't control its properties:
                m_Tracker.Add(this, rectTransform, DrivenTransformProperties.None);
                return;
            }

            m_Tracker.Add(this, rectTransform,
                (axis == 0 ? DrivenTransformProperties.SizeDeltaX : DrivenTransformProperties.SizeDeltaY));

            float size = 0;
            // Set size to min or preferred size
            size = fitting == FitMode.MinSize
                ? LayoutUtility.GetMinSize(m_Rect, axis)
                : LayoutUtility.GetPreferredSize(m_Rect, axis);
            size = axis == 0
                ? Mathf.Clamp(size, widthRange.x, widthRange.y)
                : Mathf.Clamp(size, heightRange.x, heightRange.y);
            rectTransform.SetSizeWithCurrentAnchors((RectTransform.Axis)axis, size);
        }

        /// <summary>
        /// Calculate and apply the horizontal component of the size to the RectTransform
        /// </summary>
        public void SetLayoutHorizontal()
        {
            m_Tracker.Clear();
            if(controlSelf)
                HandleSelfFittingAlongAxis(0);
        }

        /// <summary>
        /// Calculate and apply the vertical component of the size to the RectTransform
        /// </summary>
        public void SetLayoutVertical()
        {
            if(controlSelf)
                HandleSelfFittingAlongAxis(1);
        }

        protected void SetDirty()
        {
            if (!IsActive())
                return;

            LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
        }

        public bool SetStruct<T>(ref T currentValue, T newValue) where T : struct
        {
            if (EqualityComparer<T>.Default.Equals(currentValue, newValue))
                return false;

            currentValue = newValue;
            return true;
        }

#if UNITY_EDITOR
        protected override void OnValidate()
        {
            SetDirty();
        }

        protected override void Reset()
        {
            base.Reset();
            widthRange = new Vector2(0, m_Rect.rect.width);
            heightRange = new Vector2(0, m_Rect.rect.height);
        }

#endif

    }
}